// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include "coin2/strategy/pta/pnl.h"

#include <algorithm>
#include <vector>

#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"
#include "coin2/exchange/factory/symbology/product.h"
#include "coin2/strategy/pta/performance.h"

using presto::quant::feature::SingleResponseProto;

struct Stat {
  double fill_pos = 0;
  double fill_pq = 0;  // qty*entry_price (linear) or qty/entry_price (inverted)
  double maker_pq = 0;
  double taker_pq = 0;

  // out
  double pnl_unrealized = 0;
  double last_price_in_settle_currency = 0;

  double pnl_for_inverse(
      const coin2::exchange::base::symbology::IProductInfo& info,
      double last_price) {
    double maker_fee = maker_pq * info.contract_value() * info.maker_fee_rate();
    double taker_fee = taker_pq * info.contract_value() * info.taker_fee_rate();

    // long pnl = qty*(1/entry - 1/exit)
    // short pnl = qty*(1/exit - 1/entry)
    // total = sum(qty/entry*sgn) - fill_pos/exit

    double realized_pnl = fill_pq * info.contract_value();
    last_price_in_settle_currency = (last_price != 0) ? (1.0 / last_price) : 0;
    pnl_unrealized = (last_price != 0) ? (-fill_pos / last_price * info.contract_value()) : 0.0;
    double pnl_gross = realized_pnl + pnl_unrealized;
    double pnl_net = pnl_gross - maker_fee - taker_fee;

    // printf("====\n");
    // printf("     maker_fee: %f\n", maker_fee);
    // printf("     taker_fee: %f\n", taker_fee);
    // printf("  realized_pnl: %f\n", realized_pnl);
    // printf("unrealized_pnl: %f\n", unrealized_pnl);
    // printf("     pnl_gross: %f\n", pnl_gross);
    // printf("       pnl_net: %f\n", pnl_net);
    // printf("====\n");

    return pnl_net;
  }

  double pnl_for_linear(
      const coin2::exchange::base::symbology::IProductInfo& info,
      double last_price) {
    double maker_fee = maker_pq * info.contract_value() * info.maker_fee_rate();
    double taker_fee = taker_pq * info.contract_value() * info.taker_fee_rate();

    // long realized = qty*(exit - entry)
    // short realized = qty*(entry - exit)
    // total = -sum(qty*entry*sgn) + fill_pos*exit

    double realized_pnl = -fill_pq * info.contract_value();
    last_price_in_settle_currency = last_price;
    pnl_unrealized = fill_pos * last_price * info.contract_value();
    double pnl_gross = realized_pnl + pnl_unrealized;
    double pnl_net = pnl_gross - maker_fee - taker_fee;
    return pnl_net;
  }

  double pnl(const coin2::exchange::base::symbology::IProductInfo& info, double last_price) {
    if (info.is_inverse()) {
      return pnl_for_inverse(info, last_price);
    } else {
      return pnl_for_linear(info, last_price);
    }
  }

  Stat& operator+=(const Stat& other) {
    fill_pos += other.fill_pos;
    fill_pq += other.fill_pq;
    maker_pq += other.maker_pq;
    taker_pq += other.taker_pq;
    return *this;
  }
};

namespace coin2::strategy::pta {

// out: pnl_net, pnl_gross, pnl_net_sharpe_adjusted
nlohmann::json calculate_sim_pnl(
    const MarketExchangeApi& mea,
    const presto::quant::feature::OrderResponsesProto& responses,
    int64_t resolve_ts) {
  nlohmann::json report(nlohmann::json::value_t::object);
  coin2::exchange::base::symbology::impl::ProductEncyclopedia cache;

  for (const auto& product_response : responses.order_responses()) {
    const auto& symbol_info = product_response.symbol_info();
    auto product = CreateProductFromNormString(mea, symbol_info.product_name(), resolve_ts);

    Stat stat {};
    int64_t last_ts = 0;
    const auto& info = cache.holder(*product).product_info();
    double last_midp = symbol_info.last_midp();
    int num_fills = 0;

    if (info.is_inverse()) {
      // 1 contract = fixed amount of quote = e.g. multiplier (quote)
      // = worth of multiplier/price (base)
      for (const auto& entry : product_response.responses()) {
        if (entry.type() == SingleResponseProto::ORDER_FILLED) {
          last_ts = entry.time();
          num_fills++;
          auto pq = entry.fill_qty() / entry.fill_price();
          stat.fill_pq += pq * entry.sign();
          stat.fill_pos += entry.fill_qty() * entry.sign();

          if (entry.fill_type() == SingleResponseProto::MAKER) {
            stat.maker_pq += pq;
          } else if (entry.fill_type() == SingleResponseProto::TAKER) {
            stat.taker_pq += pq;
          }
        }
      }
    } else {
      // 1 contract = fixed amount of base = e.g. multiplier (base)
      // = worth of multiplier*price (quote)
      for (const auto& entry : product_response.responses()) {
        if (entry.type() == SingleResponseProto::ORDER_FILLED) {
          last_ts = entry.time();
          num_fills++;
          auto pq = entry.fill_qty() * entry.fill_price();
          stat.fill_pq += pq * entry.sign();
          stat.fill_pos += entry.fill_qty() * entry.sign();
          if (entry.fill_type() == SingleResponseProto::MAKER) {
            stat.maker_pq += pq;
          } else if (entry.fill_type() == SingleResponseProto::TAKER) {
            stat.taker_pq += pq;
          }
        }
      }
    }

    double pnl_net = stat.pnl(info, last_midp);
    report[symbol_info.product_name()] = {
        {"pnl_net", pnl_net},
        {"pnl_unrealized", stat.pnl_unrealized},
        {"last_price_in_settle_currency", stat.last_price_in_settle_currency},
        // {"sharpe", performance::sharpe(pnls)},
        // {"sortino", performance::sortino(pnls)},
        // {"profit_factor", performance::profit_factor(pnls)},
        {"num_fills", num_fills},
        {"last_ts", last_ts},
    };
  }

  return report;
}

}  // namespace coin2::strategy::pta
